package com.jiao.table.csql;

import cn.hutool.core.collection.CollectionUtil;
import com.jiao.comm.utils.CollectionNullUtils;
import com.jiao.comm.utils.SpringUtils;
import com.jiao.comm.exception.*;
import com.jiao.comm.utils.TypeConvert;
import com.jiao.syntax.CsplExpressionParser;
import com.jiao.syntax.entity.CselTemplate;
import com.jiao.syntax.exception.ExpressionException;
import com.jiao.table.cache.DynamicCacheRead;
import com.jiao.table.cache.DynamicReadMateData;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static com.jiao.syntax.enums.TokenKind.*;

/**
 * 缓存表达式计算.
 * @Author: vincent.jiao
 * @Date: 2021/4/24
 */
public class CalculationCselExpression extends ExpressionAbstract {
    DynamicReadMateData dynamicReadMateData;
    DynamicCacheRead dynamicCacheRead;

    public CalculationCselExpression(String expression) throws NoSuchMethodException {
        super(new CsplExpressionParser(expression));

        super.cselTemplates = parse.getExpressionTemplate().getCselTemplates();
        dynamicReadMateData = SpringUtils.getBean("dynamicReadMateData");
        dynamicCacheRead = SpringUtils.getBean("dynamicCacheRead");
    }

    @Override
    public <T> T getValue(Object... params) throws CacheSysException {
        Map<String, Object> paramsMap = new HashMap<>();
        if(params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                paramsMap.put(String.valueOf(i), params[i]);
            }
        }

        return getValue(paramsMap);
    }

    @Override
    public <T> T getValue(Map<String, Object> params) throws CacheSysException {
        super.params = params;

        Set<Long> resultIds = handlerExpression();
        if(CollectionUtil.isEmpty(resultIds)) {
            return null;
        }

        if(resultIds.size() > 1){
            throw new ManyResultsException("查询到多条数据, csql:" + sourceValue);
        }

        Long id = TypeConvert.objectToLong(resultIds.toArray()[0]);
        return dynamicCacheRead.getByPrimaryKey(tableName, id);
    }

    @Override
    public <T> List<T> getValueList(Object... params) throws CacheSysException {
        Map<String, Object> paramsMap = new HashMap<>();
        if(params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                paramsMap.put(String.valueOf(i), params[i]);
            }
        }

        return getValueList(paramsMap);
    }

    public <T> List<T> getValueList(Map<String, Object> params) throws CacheSysException {
        super.params = params;

        Set<Long> resultIds = handlerExpression();
        if(CollectionUtil.isEmpty(resultIds)) {
            return Collections.emptyList();
        }

        return dynamicCacheRead.getBatchByPrimaryKey(tableName, resultIds);
    }

    /**
     * 检索表达式.
     * 采用时间戳乐观锁机制查询，防止幻读.
     * @return
     */
    private Set<Long> handlerExpression(){
        if(!checkIndex()){
            throw  new ExpressionException("表达式检索只能检索索引字段");
        }

        //自旋三次
        Set<Long> result = null;
        int i = 0;
        while (true){
            i++;

            String version = dynamicReadMateData.getMateDataVersion(tableInfo.getMateDataVersionKey());

            try {
                result = filterData(cselTemplates);
            }catch (Exception e){
                throw new ReaderCacheInfoException("查询失败: "+ e.getMessage(), e);
            }

            String currVersion = dynamicReadMateData.getMateDataVersion(tableInfo.getMateDataVersionKey());
            if(version.equals(currVersion)){
                break;
            } else {
                if(i > 3){
                    throw new ConcurrentWriteException();
                }

                //数据被修改了，再次查询最新的数据
                continue;
            }
        }


        return CollectionNullUtils.getSet(result);
    }

    /**
     * 根据表达式过滤数据.
     * @param list
     * @return
     */
    private Set<Long> filterData(List<CselTemplate> list){
        if(isSelectAll()) {
            return dynamicReadMateData.getAllId(tableName);
        }

        CselTemplate template = null;
        Set<Long> resultSet = new HashSet<>();

        for (int i = 0, size = list.size(); i < size; i++){
            template = list.get(i);
            Set<Long> tmpResultSet = null;

            if(template.isStartGroup()){
                //采用递归解决括号问题与优先级问题
                int start = i;
                int lparenpPairing = 0;     //左括号配对
                CselTemplate innerTemplate = null;

                while (++i < size){
                    if((innerTemplate = list.get(i)).isStartGroup()){
                        lparenpPairing++;
                    }

                    if(innerTemplate.isEndGroup ()){
                        if(lparenpPairing == 0){
                            break;
                        }

                        continue;
                    }
                }

                //找到还没配对完毕
                if(lparenpPairing > 0){
                    throw new ExpressionException("表达式错误, 括号未闭合");
                }

                //找出括号内所有表达式之后，当作新的表达式传入递归执行
                tmpResultSet = filterData(list.subList(start+1, i));

            } else {
                boolean isPrimaryKey = tableInfo.getPrimaryKeyField().getName().equalsIgnoreCase(template.getField());
                tmpResultSet = isPrimaryKey ? calculateResultSetPimaryKey(template) : calculateResultSetIndex(template);
            }


            //合并计算结果
            if(template.getRelationIdentifier() != null){
                calculateResultSet(template, resultSet, tmpResultSet);
            } else {
                resultSet.addAll(tmpResultSet);
            }
        }

        return resultSet;
    }

    /**
     * 是否查询全部.
     * @return
     */
    public boolean isSelectAll() {
        return cselTemplates.size() == 0 && colunms.size() == 0;
    }

    /**
     * 主键结构查询.
     * @param template
     * @return
     */
    protected Set<Long> calculateResultSetPimaryKey(CselTemplate template){
        Set<Long> resultSet = null;
        List valueList = super.getTemplateValueList(template);
        Long maxIdNum = dynamicReadMateData.getMaxIdNum(tableName);

        switch (template.getOperationIdentifier()){
            case GE : //(">="),
            case GT : //(">"),
                long value = getLongValue(valueList.get(0));
                long tmpMax = GE.equals(template.getOperationIdentifier()) ? maxIdNum : maxIdNum - 1;
                resultSet = dynamicReadMateData.getBitGetExistsBit(tableName, value, tmpMax) ;
                break;

            case LE : //("<="),
            case LT : //("<"),
                value = getLongValue(valueList.get(0));
                value = LE.equals(template.getOperationIdentifier()) ? value : value - 1;
                resultSet = dynamicReadMateData.getBitGetExistsBit(tableName, 0, value) ;
                break;

            case EQ : //("="),
                value = getLongValue(valueList.get(0));
                resultSet = new HashSet<>(1);
                resultSet.add(value);
                break;

            case NE : //("!="),
                value = getLongValue(valueList.get(0));
                resultSet = dynamicReadMateData.getBitGetExistsBit(tableName, 0, maxIdNum) ;
                resultSet.remove(value);
                break;

            case BETWEEN : //("between"),
                if(valueList.size() != 2){
                    throw new ExpressionException("关键字 between 后面只能存在2个参数");
                }

                long min = TypeConvert.objectToLong(valueList.get(0));
                long max = TypeConvert.objectToLong(valueList.get(1));

                resultSet = dynamicReadMateData.getBitGetExistsBit(tableName, min, max) ;
                break;

            case IN : //("in"),
                resultSet = dynamicReadMateData.getBitGetExistsBit(tableName, valueList);
                break;

            case NOTIN : //("not in"),
                resultSet = dynamicReadMateData.getBitGetExistsBit(tableName, 0, maxIdNum) ;
                resultSet.removeAll(valueList);
                break;
        }

        return resultSet;
    }

    /**
     * 实现索引结构的查询.
     * @param template
     * @return
     */
    protected Set<Long> calculateResultSetIndex(CselTemplate template){
        Set<Long> resultSet = null;

        if(LITERAL_STRING.equals(template.getValueType())){
            switch (template.getOperationIdentifier()) {
                case GE: //(">="),
                case GT: //(">"),
                case LE: //("<="),
                case LT: //("<"),
                case BETWEEN : //("between"),
                    if (LITERAL_STRING.equals(template.getValueType())) {
                        throw new ExpressionException("不支持的表达式：" + template.getField() + template.getOperationIdentifier().getValue() + "不支持此值");
                    }
            }
        }


        switch (template.getOperationIdentifier()){
            case LE : //("<="),
            case LT : //("<"),
                Long value = getLongValue(template);
                value = LE.equals(template.getOperationIdentifier()) ? value : value - 1;
                resultSet = dynamicReadMateData.getPKSetByRangeSet(tableName, template.getField(), 0, value);

                break;

            case GE : //(">="),
            case GT : //(">"),
                value = getLongValue(template);
                value = GE.equals(template.getOperationIdentifier()) ? value : value - 1;
                resultSet = dynamicReadMateData.getPKSetByRangeSet(tableName, template.getField(), 0, value);

                break;

            case BETWEEN : //("between"),
                List list = super.getTemplateValue(template);
                if(CollectionNullUtils.isEmpty(list)){
                    throw new ExpressionException("表达式错误：between 值列表必须存在2个值");
                }

                Long min = TypeConvert.objectToLong(list.get(0)),
                     max = TypeConvert.objectToLong(list.get(1));
                resultSet = dynamicReadMateData.getPKSetByRangeSet(tableName, template.getField(), min, max + 1);
                break;

            case EQ : //("="),
                Object valueObj = super.getTemplateValue(template);
                resultSet = dynamicReadMateData.getPKSetByIndexValue(tableName, template.getField(), valueObj);
                break;

            case NE : //("!="),
                valueObj = super.getTemplateValue(template);
                Set set = dynamicReadMateData.getMateDateIndexValueSet(tableName, template.getField());

                if(set != null){
                    set.remove(valueObj+"");    //都将值认为是字符串
                    resultSet = dynamicReadMateData.getPKSetByIndexValueBatch(tableName, template.getField(), set);
                }

                break;

            case IN : //("in"),
                list = super.getTemplateValue(template);
                if(CollectionNullUtils.isEmpty(list)){
                    throw new ExpressionException("表达式错误：in 没有值");
                }

                set = dynamicReadMateData.getMateDateIndexValueSet(tableName, template.getField());

                if(set != null){
                    Set newResult = new HashSet(set.size());
                    for (Object o : list){
                        if(set.contains(o)){
                            newResult.add(o);
                        }
                    }

                    resultSet = dynamicReadMateData.getPKSetByIndexValueBatch(tableName, template.getField(), newResult);
                }

                break;

            case NOTIN : //("not in"),
                list = super.getTemplateValue(template);;
                if(CollectionNullUtils.isEmpty(list)){
                    throw new ExpressionException("表达式错误：in 没有值");
                }

                set = dynamicReadMateData.getMateDateIndexValueSet(tableName, template.getField());
                if(set != null){
                    set.removeAll(list);
                    resultSet = dynamicReadMateData.getPKSetByIndexValueBatch(tableName, template.getField(), set);
                }

                break;

            default:
                throw new ExpressionException("表达式错误："+template.getField()+template.getOperationIdentifier().getValue());
        }

        return  TypeConvert.setToLong(resultSet);
    }


    protected Set<Long> calculateResultSet(CselTemplate template, Set<Long> resultSet){
        return resultSet;
    }

    protected Set<Long> calculateResultSet(CselTemplate template, Set<Long> resultSet
                                        , Set<Long> targetSet){
        if(AND.equals(template.getRelationIdentifier())){
            and(resultSet, targetSet);

        } else if (OR.equals(template.getRelationIdentifier())){
            or(resultSet, targetSet);
        }

        return resultSet;
    }

    /**
     * 取交集.
     * @param s1
     * @param s2
     * @return
     */
    protected Set<Long> and(Set s1, Set s2){
        s1.retainAll(s2);
        return s1;
    }

    /**
     * 去重.
     * @param s1
     * @param s2
     * @return
     */
    protected Set<Long> or(Set s1, Set s2){
        s1.addAll(s2);
        return s1;
    }

    private boolean checkIndex(){
        Collection<String> keys = tableInfo.getAllIndexFieldName();

        boolean flag = false;
        for (CselTemplate item : cselTemplates){
            if(item.getField() == null){
                continue;
            }

            flag = tableInfo.getPrimaryKeyField().getName().equals(item.getField());
            if(!keys.contains(item.getField()) && !flag){
                return false;
            }
        }

        return true;
    }

    public Long getLongValue(CselTemplate template){
        return TypeConvert.objectToLong(super.getTemplateValue(template));
    }

    public Long getLongValue(Object val){
        return TypeConvert.objectToLong(val);
    }
}
